/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package infoplaces2;

import auxiliares.HistogramaNovo;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import javax.imageio.ImageIO;

/**
 *
 * @author andre
 */
public class ProcessedStreetNode<EnvironmentMapType extends PanoramicImage, NeighbourIDType> {

    private final StreetNode<EnvironmentMapType, NeighbourIDType> streetNode;
    private final PlanarPanoramicView planarPanoramicView;
    private double degreeMaximum;
    private final int matchesCount;
    private float[] pontos;
    private double degreeMaximumWithoutNorth;

    public ProcessedStreetNode(StreetNode<EnvironmentMapType, NeighbourIDType> streetNode, Photo photo, int sizeBin, int sizeWindow) throws IOException, InterruptedException {

        CubicPanoramicImage dummy = new CubicPanoramicImage((EquirectangularPanoramicImage) streetNode.getEnvironmentMap());

        this.streetNode = streetNode;
        this.planarPanoramicView = new PlanarPanoramicView(dummy);

        try (BufferedReader matches = execASIFT((BufferedImage) this.planarPanoramicView.getImage(0), photo, (String) streetNode.getID())) {

            this.matchesCount = Integer.parseInt(matches.readLine());
            System.out.println("matchesCount: " + matchesCount);

            if (matchesCount > 0) {
                this.degreeMaximum = createHistogram((String) this.streetNode.getID(), this.planarPanoramicView.getImage(0).getWidth(), ((GoogleStreetNode) this.streetNode).north, sizeBin, sizeWindow);
            } else {//para fins de debug
                if (matchesCount == 0) {
                    System.out.println("Nenhum match foi encontrado! matches: " + matchesCount);
                } else {
                    System.out.println("A vaca foi pro brejo! matches: " + matchesCount);
                }
                this.degreeMaximum = -1;
            }
            //TODO Não pode ser feito casting dos objetos
            //Saber qual bin (angulo) é o mais votado
        }

    }

    public StreetNode<EnvironmentMapType, NeighbourIDType> getStreetNode() {
        return streetNode;
    }

    public float[] getHistograma() {
        return this.pontos;//o histograma é o vetor de pontos em si
    }
    
    public double getDegreeMaximumWithoutNorth(){
        return this.degreeMaximumWithoutNorth;
    }

    public double getDegreeMaximum() {
        return this.degreeMaximum;
    }

    public int getMacthesCount() {
        return this.matchesCount;
    }

    private static BufferedReader execASIFT(BufferedImage panorama, Photo photo, String pano_id) throws InterruptedException, IOException {

        File tempDir = new File("temp");
        tempDir.mkdir(); // Cria dirtetório temporário caso não exista.

        File panoramaFile = new File(tempDir, pano_id + "_panorama.png");
        File photoFile = new File(tempDir, pano_id + "_photo.png");
        File comp1File = new File(tempDir, pano_id + "_comp1.png");
        File comp2File = new File(tempDir, pano_id + "_comp2.png");
        File matchesFile = new File(tempDir, pano_id + "_matches.txt");
        File panoramaFeaturesFile = new File(tempDir, pano_id + "_panorama_features.txt");
        File photoFeaturesFile = new File(tempDir, pano_id + "_photo_features.txt");

        ImageIO.write(photo.getImage(), "png", photoFile);
        ImageIO.write(panorama, "png", panoramaFile);

        String params = panoramaFile + " "
                + photoFile + " "
                + comp1File + " "
                + comp2File + " "
                + matchesFile + " "
                + panoramaFeaturesFile + " "
                + photoFeaturesFile;

        //System.getenv()
        /**/
        Runtime.getRuntime().exec("./ASIFT.bin " + params).waitFor(); // Linux
        /*/
         Runtime.getRuntime().exec("cmd /c prog_asift " + params).waitFor(); // Windows
         /**/

        return new BufferedReader(new FileReader(matchesFile));

    }

    private double createHistogram(String panoId, float larguraP, double north, int sizeBin, int sizeWindow) throws FileNotFoundException, IOException {

        float larguraPanorama = (larguraP / 4); //Utilizado na regra de 3 para converter ponto em grau
        int[] conteudo = new int[360 / sizeBin];//Conteúdo do histograma (todos os valores)
        /*ex.: | 2 | 3 | 0 | ... | 2 |
         0   1   2        359
         para o angulo 0º tem 2 votos, 1º tem 3, 2º nao tem votos... 359º tem 2 votos*/

        int[] grausConvertidos = new int[100000];//para fins de debug
        pontos = lerPontos(panoId); //preeche o vetor PONTOS com os valores da coordenada X da imagem panoramica planificada do google
        for (int i = 0; i < pontos.length; i++) {
            //todos os pontos tinha sido convertidos para coordenadas cartesianas, agr é preciso converter para grau novamente, a fim de fazer a triangulação de forma correta, pois os arcos sao iguais, as projeções no cubo, nao.
            grausConvertidos[i] = converterEmGraus(pontos[i], sizeBin, larguraPanorama) / sizeBin; //divide por sizeBin, pois caso venhamos a trabalhar com bins em intervalos maiores do que 1º os angulos estarão dentro de uma região no vetor. Ex.: Se o bin tivesse tamanho 10º, o angulo 32º ocuparia a posição 3 do vetor resultante, que possuiria tamanho 36
            conteudo[grausConvertidos[i]] = conteudo[grausConvertidos[i]] + 1; //aumenta o numero de votos para bin a qual o angulo da vez pertence. conteudo terá basicamente as informações do histograma, a quantidade de votos que cada bin recebeu
        }
        print(pontos, grausConvertidos);//para fins de debug
        
        degreeMaximum = calculateDegreeMaximum(conteudo, north, sizeBin, sizeWindow);
        System.out.println("Grau Máximo com Base Norte: "+degreeMaximum);
        
        salvaHistograma(panoId, conteudo, sizeBin, this.getMacthesCount(), north, this.getDegreeMaximumWithoutNorth(), degreeMaximum);
        return degreeMaximum; // para se fazer a triangulação de fato, é preciso que o angulo não esteja apenas em coordenadas geograficas, seu norte precisa ter a mesma referencia que o norte global, mas essa conversao só é necessária para o angulo vencedor!
    }

    private void print(float[] pontos, int[] grausConvertidos) {
        System.out.println("Pontos\tGraus");
        for (int i = 0; i < pontos.length; i++) {
            System.out.println(pontos[i] + "\t" + grausConvertidos[i]);

        }
    }

    /*Este Metodo le do arquivo de matches referentes ao panorama e inicializa
     * o array de pontos, com os valores encontrados nesse arquivo
     */
    private float[] lerPontos(String panoId) throws FileNotFoundException, IOException {

        BufferedReader buf = new BufferedReader(new FileReader(new File("temp/" + panoId + "_matches.txt")));

        int numeroTotalMatches = Integer.parseInt(buf.readLine()); //le a primeira linha do arquivo, esta contem o numero total de matches
        float[] pontos = new float[numeroTotalMatches]; //cria um vetor de pontos para armazenar todas as coordenadas X da foto panoramica onde foram encontrados matches de tamanho igual ao numero total de matches encontrados

        for (int i = 0; i < numeroTotalMatches; i++) {
            //le a linha i do arquivo de matches, mas só pega o primeiro numero, que é o referente a coordenada X da foto panoramica equirectangular do google e coloca no vetor de pontos
            String linha = buf.readLine();
            int index = linha.indexOf(" ");

            String valor = linha.substring(0, index);

            pontos[i] = Float.parseFloat(valor);
        }
        return pontos;
    }

    //Regra de 3 para converter um ponto do eixo x, em grau
    private int converterEmGraus(float x, int numAgrupador, float larguraPanorama) {
        //Caulca o grau o convertendo no eixo x (entre -1 e 1) e somando a rotacao (se necessario)
        int grau = (int) Math.toDegrees(Math.atan(converteOEspaco(x, larguraPanorama))) + (verificaLado(x, larguraPanorama) * 90);

        //AGRUPA ENTRE VALORES
        int grauAgrupado = (grau / numAgrupador) * numAgrupador;

        //Verifica se o numero e negativo, caso seja, utiliza o complementar a 360 pos se trata de um circulo
        return (grauAgrupado >= 0 ? grauAgrupado : 360 + grauAgrupado);
    }

    private float converteOEspaco(float x, float larguraPanorama) {
        return ((2 * (x - (larguraPanorama * verificaLado(x, larguraPanorama)))) - larguraPanorama) / larguraPanorama;
    }

    private int verificaLado(float x, float larguraPanorama) {
        int posicaoLado;

        if (x < larguraPanorama) {
            posicaoLado = 0;
        } else {
            if ((x > larguraPanorama) && (x < (2 * larguraPanorama))) {
                posicaoLado = 1;
            } else {
                if ((x > (2 * larguraPanorama)) && (x < (3 * larguraPanorama))) {
                    posicaoLado = 2;
                } else {
                    posicaoLado = 3;
                }
            }
        }

        return posicaoLado;
    }

    private double calculateDegreeMaximum(int[] conteudo, double north, int sizeBin, int sizeWindow) {
        //USAR ARRAYLIST para lista circular
        double somaAnterior = Double.MIN_VALUE;
        double somaAtual = Double.MIN_VALUE;
        int index = 0;

        for (int i = 0; i < conteudo.length; i++) {
            somaAtual = calculaSomaJanelaComPesos(createWindow(conteudo, sizeWindow, i));
            if (somaAtual > somaAnterior) {
                somaAnterior = somaAtual;
                index = i;
            }
            System.out.println("\n***********************************************\n");
        }

        this.degreeMaximumWithoutNorth = index * sizeBin;
        System.out.println("Maior soma: " + somaAnterior);
        System.out.println("Índice que possui a maior soma de janela: " + index);
        System.out.println("Maior angulo sem Norte: " + degreeMaximumWithoutNorth);

        return converteGrauComBaseNorte(north, degreeMaximumWithoutNorth);
    }

    private double converteGrauComBaseNorte(double north, double degreeMaximumWithoutNorth) {
        double grauBaseNorte = (360 - north) + degreeMaximumWithoutNorth;
        return grauBaseNorte < 360 ? grauBaseNorte : (grauBaseNorte - 360);
    }

    private  double[] createWindow(int[] conteudo, int sizeWindow, int i) {
        CircularList circularList = new CircularList(conteudo);

        System.out.println("Vetor Conteudo (possui o numero de votos/bin");
        circularList.print(); //para debug
        double[] vetor = new double[sizeWindow];

        int cont = 0;
        //o i é o ponto central da janela, o j vai
        if (sizeWindow % 2 == 0) { //tamanho da janela é par
            for (int j = i - sizeWindow / 2 + 1; j <= i + sizeWindow / 2; j++) { //anda da esquerda para a direita
                vetor[cont] = circularList.get(j);
                cont++;
            }
        } else {
            for (int j = i - sizeWindow / 2; j <= i + sizeWindow / 2; j++) { //anda da esquerda para a direita
                vetor[cont] = circularList.get(j);
                cont++;
            }
        }
        System.out.println("Janela");
        print(vetor); //para debug

        return vetor;
    }

    private double calculaSomaJanelaComPesos(double[] vetor) {
        double soma = 0;
        int sizeWindow = vetor.length;
        //new double[sizeWindow];
        if (sizeWindow == 2) {
            soma = vetor[0] * 0.6 + vetor[1] * 0.4;
        } else {
            soma = soma + (vetor[0] * 1 / (2 * sizeWindow)) + (vetor[vetor.length - 1] * 1 / (2 * sizeWindow));
            for (int i = 1; i < vetor.length - 1; i++) {
                if (i == vetor.length / 2) {
                    soma = soma + (vetor[i] * 2 / sizeWindow);
                } else {
                    soma = soma + (vetor[i] * 1 / sizeWindow);
                }
            }
        }
        System.out.println("soma = " + soma + "\n");
        return soma;
    }

    public void print(double[] vetor) {
        for (int i = 0; i < vetor.length; i++) {
            System.out.println("vetor[" + i + "] = " + vetor[i]);
        }
        System.out.println("");
    }

    private void salvaHistograma(String panoId, int[] conteudo, int numAgrupador, int numeroTotalMatches, double north, double grauComMaisMatchesSemONorte, double grauComMaisMatches) throws FileNotFoundException, IOException {
        File arq = new File("temp/" + panoId + "_histograma.txt");

         FileWriter fw = new FileWriter(arq);
         BufferedWriter bw = new BufferedWriter(fw);

         int grau = 0;
         for (int matche : conteudo) {
         bw.write(grau + ": " + matche);
         bw.newLine();
         grau = grau + numAgrupador;
         }

         bw.write("Numero Total de Matches: " + numeroTotalMatches);
         bw.newLine();
         bw.write("Norte: " + north);
         bw.newLine();
         //        bw.write("A conta para o calculo do angulo com maior numero de matches : "
         //                + "(360 - Norte) + Angulo mais matches");
         //        bw.newLine();
         //        bw.write("360 - Norte = " + (350 - norte));
         //        bw.newLine();
         bw.write("Angulo maior numero matches (sem o norte): " + grauComMaisMatchesSemONorte);
         bw.newLine();
         bw.write("Angulo maior numero matches : " + grauComMaisMatches);
         bw.close();
    }

}


